#include "VTTConfiguration.h"
#include "VTTCommon.h"
#include "VTTErrorManager.h"

#include <fstream>

using std::ifstream;
using std::ofstream;
using std::string;
using std::vector;

/**
  * Constructs a new instance of the VTTConfiguration Class. This instance is
  * a Null-Object.
  */
VTTConfiguration::VTTConfiguration( void )
{
	Reset();
}

/**
  *	Constructs a new instance of the VTTConfiguration Class with the parameters
  * in the configOptions.
  *
  * @param configOptions parameters for the configuration
  */
VTTConfiguration::VTTConfiguration( const VTTConfigurationOptions &configOptions )
{
	SetParameters( configOptions );
}

/**
  * Constructs a new instance of the VTTConfiguration Class with default values.
  * Calls the SetDefaultValues for that purpose, look at its comment block for details.
  *
  * @param basePath pointer to a string containing the base path (where all the images for the Atlas reside)
  */
VTTConfiguration::VTTConfiguration( const string *basePath )
{
	SetDefaultValues( *basePath );
}

/**
  * Constructs a new instance of the VTTConfiguration Class. The values of the configuration file
  * whose path is passed as an argument are used. Therefore the LoadFromFile()-method is called.
  * If something goes wrong (invalid values, missing arguments etc.) all member fields are reset
  * and the configuration object becomes a Null-object. An error message is then pushed to the ErrorManager.
  *
  * @param fileName path of the configuration file
  */
VTTConfiguration::VTTConfiguration( const string &fileName )
{
	if (IsFile( fileName ))
	{
		LoadFromFile( fileName );
	} else
	{
		//cout << "ERROR: Configuration file " << fileName << " does not exist"
		//	 << endl;

		VTTErrorManager::Get()->pushErrorMessage( "VTTConfiguration::VTTConfiguration(const string &): Configuration file " + fileName + " does not exist" );
		Reset();
	}
}

/**
  * Loads the Configuration from file and sets all member fields to the values
  * provided in the file. If something goes wrong (invalid file path,
  * invalid values etc.) all member fields are reset and the configuration object becomes
  * a Null-object. An error message is then pushed to the ErrorManager.
  *
  * @param fileName path of the configuration file
  * @return Boolean value indicating whether loading was successful or not
  */
bool VTTConfiguration::LoadFromFile( const string &fileName )
{
	ifstream file;

	file.open(fileName.c_str());

	if (!file)
	{
		//cout << "ERROR: Could not read configuration file " << fileName
		//	<< endl;

		VTTErrorManager::Get()->pushErrorMessage( "VTTConfiguration::VTTConfiguration(const string &): Could not read configuration file " + fileName + " [ERRNO: " + UINT32ToString( errno ) + "]" );

		Reset();

		return false;
	}

// 	mKernelSize = 4U;
// 	mKernelSigma = 0.5f;

	SetDefaultValues( GetBasePathOfFile( fileName ) );

	string line_read;

	while (std::getline( file, line_read )) // Read line by line
	{
		if (StringStartsWith( line_read, "atlasName" ))
		{
			mAtlasName = GetStrippedStringAfterChar( line_read, '=' );
		}
		else if (StringStartsWith( line_read, "tileStoreName" ))
		{
			mTileStoreName = GetStrippedStringAfterChar( line_read, '=' );
		}
		else if (StringStartsWith( line_read, "maxAtlasPartSize" ))
		{
			mMaxAtlasPartSize =
				StdStringToUINT32(
					GetStrippedStringAfterChar( line_read, '=' ) );
		}
		else if (StringStartsWith( line_read, "quality" ))
		{
			mPictureQuality =
				StdStringToUINT32(
					GetStrippedStringAfterChar( line_read, '=' ) );
		}
		else if (StringStartsWith( line_read, "border" ))
		{
			mBorder =
				StdStringToUINT32(
					GetStrippedStringAfterChar( line_read, '=' ) );
		}
		else if (StringStartsWith( line_read, "tileSize" ))
		{
			mTileSize =
				StdStringToUINT32(
					GetStrippedStringAfterChar( line_read, '=' ) );
		}
		else if (StringStartsWith( line_read, "kernelSize" ))
		{
			mKernelSize =
				StdStringToUINT32(
				GetStrippedStringAfterChar( line_read, '=' ) );
		}
		else if(StringStartsWith( line_read, "sigma" ))
		{
			mKernelSigma = StdStringToFloat(
				GetStrippedStringAfterChar( line_read, '=' ) );
		}
		else if (StringStartsWith( line_read, "tilesFormat" ))
		{
			string tiles_format = GetStrippedStringAfterChar( line_read, '=' );

			if ( "jpg" == tiles_format || "jpeg" == tiles_format )
			{
				mTilesFormat = VTT_IMAGE_JPEG;
			} else if ( "png" == tiles_format )
			{
				mTilesFormat = VTT_IMAGE_PNG;
			}
		}
		else if (StringStartsWith( line_read, "atlasFormat" ))
		{
			string atlas_format = GetStrippedStringAfterChar( line_read, '=' );

			if ( "jpg" == atlas_format || "jpeg" == atlas_format )
			{
				mAtlasFormat = VTT_IMAGE_JPEG;
			} else if ( "png" == atlas_format )
			{
				mAtlasFormat = VTT_IMAGE_PNG;
			}
		}
		else if (StringStartsWith( line_read, "imageBlock" ))
		{
			string image_block = GetStrippedStringAfterChar( line_read, '=' );
			vector<string> images;

			Tokenize( image_block, images, ":" );

			UINT32 xn = StdStringToUINT32( images[0] );
			UINT32 yn = StdStringToUINT32( images[1] );

			if (images.size() - 2 != xn * yn)
			{
				// Wrong number of images
				VTTErrorManager::Get()->pushErrorMessage( "VTTConfiguration::VTTConfiguration(const string &): Wrong number of images in image block, must be " + UINT32ToString(xn * yn) );
				Reset();

				return false;
			}

			mImageBlockWidth = xn;
			mImageBlockHeight = yn;

			mImageBlockImageNames.assign( ++(++images.begin()), images.end() );
		}
	}

	// Check all values

	if ((!IsDir( mBasePath + VTT_FILE_SEPARATOR + mAtlasName ) &&
		!MakeDir(mBasePath + VTT_FILE_SEPARATOR + mAtlasName))
		
		||
		
		(!IsDir( mBasePath + VTT_FILE_SEPARATOR + mTileStoreName ) &&
		!MakeDir(mBasePath + VTT_FILE_SEPARATOR + mTileStoreName))
		
		||

		!IsValidTileSize( mTileSize ) || !IsValidBorderSize( mBorder ) ||
		mPictureQuality < 0 || mPictureQuality > 100 ||
		mAtlasFormat == VTT_IMAGE_NOT_SPECIFIED ||
		mTilesFormat == VTT_IMAGE_NOT_SPECIFIED ||
		mMaxAtlasPartSize < mTileSize ||
		mKernelSize < 1 ||
		mKernelSigma <= 0.0f )
	{
		//cout << "ERROR: Invalid Configuration-File " << fileName << endl;
		VTTErrorManager::Get()->pushErrorMessage( "VTTConfiguration::VTTConfiguration(const string &): Invalid configuration file " + fileName );
		Reset();

		return false;
	} else
	{
		mIsNull = false;
		mFileName = GetFileNameFromFullPath( fileName );
		//cout << "INFO: Valid Configuration-File successfully parsed" << endl;
	}
	
	return true;
}

/**
  * Returns the name (not the path!) of the directory containing the Virtual Texture Atlas
  *
  * @return the name of the directory containing the Virtual Texture Atlas
  */
const string &VTTConfiguration::GetAtlasName( void ) const
{
	return mAtlasName;
}

/**
  * Returns the path of the base directory, containing all images for the Atlas as well
  * as the directories for the Atlas and the Tile Store
  *
  * @return path of the base directory
  */
const string &VTTConfiguration::GetBasePath( void ) const
{
	return mBasePath;
}

/**
  * Returns the name (not the path!) of the directory containing the Tile Store 
  *
  * @return the name of the directory containing the Tile Store
  */
const string &VTTConfiguration::GetTileStoreName( void ) const
{
	return mTileStoreName;
}

/**
  * Returns the width of the overlapping region between consecutive tiles, value in [0..4]
  *
  * @return the width of the border
  */
UINT32 VTTConfiguration::GetBorder( void ) const
{
	return mBorder;
}

/**
  * Returns the number of images in the vertical direction in the optional imageBlock
  *
  * @return number of images in the vertical direction in the optional imageBlock
  */
UINT32 VTTConfiguration::GetImageBlockHeight( void ) const
{
	return mImageBlockHeight;
}

/**
  * Returns the number of images in the horizontal direction in the optional imageBlock
  *
  * @return number of images in the horizontal direction in the optional imageBlock
  */
UINT32 VTTConfiguration::GetImageBlockWidth( void ) const
{
	return mImageBlockWidth;
}

/**
  * Returns the names of all the images in the optional imageBlock as a const reference
  * to an std::vector containing these names. The names are ordered from
  * left to right, top to bottom.
  *
  * @return names of all the images in the optional imageBlock
  */
const vector<string> &VTTConfiguration::GetImageBlockImageNames( void ) const
{
	return mImageBlockImageNames;
}

/**
  * Returns the root mean square deviation of the gaussian function used for scaling of the images
  *
  * @return root mean square deviation of the gaussian function used for scaling of the images
  */
float VTTConfiguration::GetKernelSigma( void ) const
{
	return mKernelSigma;
}

/**
  * Returns the side length of the gaussian filter kernel that is used for scaling of the images
  *
  * @return side length of the gaussian filter kernel that is used for scaling of the images
  */
UINT32 VTTConfiguration::GetKernelSize( void ) const
{
	return mKernelSize;
}

/**
  * Returns the maximum of the side length for a single part of the Virtual Texture Atlas
  *
  * @return maximum of the side length for a single part of the Virtual Texture Atlas
  */
UINT32 VTTConfiguration::GetMaxAtlasPartSize( void ) const
{
	return mMaxAtlasPartSize;
}

/**
  * Returns the quality parameter for the Jpeg compression algorithm (the higher, the better), value in [0..100]
  *
  * @return quality parameter for the Jpeg compression algorithm
  */
UINT32 VTTConfiguration::GetPictureQuality( void ) const
{
	return mPictureQuality;
}

/**
  * Returns the side length of a single tile, possible values are 64, 128, 256 and 512
  *
  * @return side length of a single tile
  */
UINT32 VTTConfiguration::GetTileSize( void ) const
{
	return mTileSize;
}

/**
  * Returns an enum value corresponding to the file format, that is used to store the Atlas parts
  *
  * @return enum value corresponding to the file format, that is used to store the Atlas parts
  */
VTTImageFormat VTTConfiguration::GetAtlasFormat( void ) const
{
	return mAtlasFormat;
}

/**
  * Returns an enum value corresponding to the file format, that is used to store the tiles in the Tile Store
  *
  * @return enum value corresponding to the file format, that is used to store the tiles in the Tile Store
  */
VTTImageFormat VTTConfiguration::GetTilesFormat( void ) const
{
	return mTilesFormat;
}

/**
  * Sets all member fields to a default value, which are:
  *
  *	mAtlasName = "TextureAtlas";
  * mBasePath = basePath;
  * mTileStoreName = "TileStore";

  * mBorder = 0;
  * mMaxAtlasPartSize = 4096;
  * mPictureQuality = 100;
  * mTileSize = 128;

  * mTilesFormat = VTT_IMAGE_JPEG;
  * mAtlasFormat = VTT_IMAGE_JPEG;

  * mImageBlockHeight = 0;
  * mImageBlockWidth = 0;
  *
  * The Configuration is of course a valid object (and not a Null-object) after that.
  *
  * @param basePath path of the base directory, containing all images for the Atlas as well as the directories for the Atlas and the Tile Store
  */
void VTTConfiguration::SetDefaultValues( const string &basePath )
{
	mIsNull = false;
	mAtlasName = "TextureAtlas";
	mBasePath = basePath;
	mTileStoreName = "TileStore";

	mBorder = 0;
	mMaxAtlasPartSize = 4096;
	mPictureQuality = 100;
	mTileSize = 128;

	mTilesFormat = VTT_IMAGE_JPEG;
	mAtlasFormat = VTT_IMAGE_JPEG;

	mImageBlockHeight = 0;
	mImageBlockWidth = 0;
	mImageBlockImageNames.clear();
}

/**
  *	Sets all the parameters of the object to the ones in configOptions
  *
  * @param configOptions parameters for the configuration
  */
bool VTTConfiguration::SetParameters( const VTTConfigurationOptions &configOptions )
{
	mAtlasName = configOptions.mAtlasName;
	mBasePath = configOptions.mBasePath;
	mTileStoreName = configOptions.mTileStoreName;
	mBorder = configOptions.mBorder;
	mMaxAtlasPartSize = configOptions.mMaxAtlasPartSize;
	mPictureQuality = configOptions.mPictureQuality;
	mTileSize = configOptions.mTileSize;
	mKernelSize = configOptions.mKernelSize;
	mKernelSigma = configOptions.mKernelSigma;
	mImageBlockWidth = configOptions.mImageBlockWidth;
	mImageBlockHeight = configOptions.mImageBlockHeight;
	mImageBlockImageNames = configOptions.mImageBlockImageNames;
	mTilesFormat = configOptions.mTilesFormat;
	mAtlasFormat = configOptions.mAtlasFormat;
	mFileName = "";

	if ((!IsDir( mBasePath + VTT_FILE_SEPARATOR + mAtlasName ) &&
		!MakeDir(mBasePath + VTT_FILE_SEPARATOR + mAtlasName))

		||

		(!IsDir( mBasePath + VTT_FILE_SEPARATOR + mTileStoreName ) &&
		!MakeDir(mBasePath + VTT_FILE_SEPARATOR + mTileStoreName))

		||

		!IsValidTileSize( mTileSize ) || !IsValidBorderSize( mBorder ) ||
		mPictureQuality < 0 || mPictureQuality > 100 ||
		mAtlasFormat == VTT_IMAGE_NOT_SPECIFIED ||
		mTilesFormat == VTT_IMAGE_NOT_SPECIFIED ||
		mMaxAtlasPartSize < mTileSize ||
		mKernelSize < 1 ||
		mKernelSigma <= 0.0f )
	{
		//cout << "ERROR: Invalid Configuration-File " << fileName << endl;
		VTTErrorManager::Get()->pushErrorMessage( "VTTConfiguration::SetParameters( const VTTConfigurationOptions &): Invalid configuration options" );
		Reset();

		return false;
	} else
	{
		mIsNull = false;
		return true;
	}
}

/**
  *	Writes the Configuration into a file on the hard disc into the base path
  * and returns the chosen file path or an empty string when the procedure failed
  *
  * @return full file path
  */
std::string VTTConfiguration::WriteToDisc( void )
{
	if (IsNull())
	{
		return "";
	}

	string file_name_prefix = "VTT-Config_";
	string file_name = "";
	UINT32 suffix_number = 1U;

	// Find free file name
	for (;;suffix_number++)
	{
		file_name = file_name_prefix + UINT32ToString( suffix_number ) + ".txt";
		if (!IsFile( mBasePath + VTT_FILE_SEPARATOR + file_name))
		{
			mFileName = file_name;
			break;
		}
	}

	// Write Configuration to hard disc
	ofstream file_stream;
	file_stream.open( mBasePath + VTT_FILE_SEPARATOR + mFileName );
	
	if (file_stream)
	{
		file_stream << "# Config-File for the VT-Tools by Michael Birsak\n\n";
		
		// Atlas directory name
		file_stream << "# Name of the Atlas directory\n";
		file_stream << "atlasName = " + mAtlasName + "\n\n";

		// Tile Store directory name
		file_stream << "# Name of the Tile Store directory\n";
		file_stream << "tileStoreName = " + mTileStoreName + "\n\n";

		// Max. Atlas Part Size
		file_stream << "# Maximum Image Size (side length in pixels) of the parts of the Texture Atlas\n";
		file_stream << "maxAtlasPartSize = " + UINT32ToString( mMaxAtlasPartSize ) + "\n\n";

		// Border
		file_stream << "# Border width of the VT Tiles in pixels (just needed when Bilinear Filtering is used)\n";
		file_stream << "border = " + UINT32ToString( mBorder ) + "\n\n";

		// JPEG Quality
		file_stream << "# Picture quality of JPEG files. Integer Number in [0..100]\n";
		file_stream << "quality = " + UINT32ToString( mPictureQuality ) + "\n\n";

		// TileSize
		file_stream << "# Size (sidelength in pixels) of the VT Tiles, must be in {64, 128, 256, 512}\n";
		file_stream << "tileSize = " + UINT32ToString( mTileSize ) + "\n\n";

		// AtlasFormat
		file_stream << "# Format of the Texture Atlas, must be in {jpg, png}\n";
		file_stream << string("atlasFormat = ") + (mAtlasFormat == VTT_IMAGE_JPEG ? "jpg" : "png") + "\n\n";

		// TilesFormat
		file_stream << "# Format of the Virtual Texture Tiles, must be in {jpg, png}\n";
		file_stream << string("tilesFormat = ") + (mTilesFormat == VTT_IMAGE_JPEG ? "jpg" : "png") + "\n\n";

		// KernelSize
		file_stream << "# Size (side length in pixels) of the kernel used for filtering of the images\n";
		file_stream << "kernelSize = " + UINT32ToString( mKernelSize ) + "\n\n";

		// Sigma
		file_stream << "# Sigma (root mean square deviation) of the gauss function used for filtering\n";
		file_stream << "sigma = " + FloatToString( mKernelSigma );

		// Image Block
		if (mImageBlockHeight != 0 && mImageBlockWidth != 0)
		{
			file_stream << "\n\n";

			file_stream << "# Image Block\n";
			file_stream << "imageBlock = " + UINT32ToString( mImageBlockWidth ) + ":" + UINT32ToString( mImageBlockHeight );
			for (vector<string>::const_iterator it = mImageBlockImageNames.begin();
				it != mImageBlockImageNames.end(); it++)
			{
				file_stream << ":" << *it;
			}
		}

		file_stream.close();

		return mBasePath + VTT_FILE_SEPARATOR + mFileName;
	}
	else
		return "";
}

/**
  * Returns a boolean value indicating whether the passed argument is the path
  * of a non-empty image directory. An image directory is non-empty when
  * it includes at least one PNG or JPEG file.
  *
  * @param imageDirectoryPath full path of the image directory
  * @return boolean value indicating whether the passed argument is the path of a non-empty image directory
  */
bool VTTConfiguration::IsNonEmptyImageDirectory( const std::string &imageDirectoryPath )
{
	if (GetAllImageNamesInsideDir( imageDirectoryPath ).size() > 0)
		return true;

	return false;
}

/**
  * Returns a boolean value indicating whether the passed argument is a valid
  * value for the border or not. The border has to in [0..4] to be valid.
  *
  * @param border width of the overlapping region between consecutive tiles
  * @return boolean value indicating whether the passed argument is a valid value for the border or not
  */
bool VTTConfiguration::IsValidBorderSize( UINT32 border )
{
	if (border == 0 || border == 1 || border == 2 || border == 3 ||
		border == 4)
		return true;

	return false;
}

/**
  * Returns a boolean value indicating whether the passed argument is a valid
  * value for the tileSize or not. The tileSize has to be 64, 128, 256 or 512 to be valid.
  *
  * @param tileSize side length of a single tile
  * @return boolean value indicating whether the passed argument is a valid value for the tileSize or not
  */
bool VTTConfiguration::IsValidTileSize( UINT32 tileSize )
{
	if (tileSize == 64 || tileSize == 128 || tileSize == 256 ||
		tileSize == 512)
		return true;

	return false;
}

/**
  * Resets all the member fields. The Configuration is a Null-object after a Reset.
  */
void VTTConfiguration::Reset( void )
{
	mIsNull = true;
	mAtlasName = "";
	mBasePath = "";
	mFileName = "";
	mTileStoreName = "";

	mBorder = 0;
	mMaxAtlasPartSize = 0;
	mPictureQuality = 0;
	mTileSize = 0;

	mTilesFormat = VTT_IMAGE_NOT_SPECIFIED;
	mAtlasFormat = VTT_IMAGE_NOT_SPECIFIED;

	mImageBlockHeight = 0;
	mImageBlockWidth = 0;
	mImageBlockImageNames.clear();
}